<?php
    /*

     Version: MPL 1.1

     Software distributed under the License is distributed on an "AS IS"
     basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
     License for the specific language governing rights and limitations
     under the License.

     The Original Code is KASSIOP Framework.

     The Initial Developer of the Original Code is SURIAN Nicolas (aka NairuS) <me@nairus.fr>.
     Portions created by the Initial Developer are Copyright (C) 2010
     The Initial Developer. All Rights Reserved.

     Contributor(s):

     Alternatively, the contents of this file may be used under the terms
     of the MPL license Version 1.1 (the  "MPL" License"), in which case the
     provisions of Version: MPL 1.1 License are applicable instead of those
     above.  If you wish to allow use of your version of this file only
     under the terms of the MPL License and not to allow others to use
     your version of this file under the MPL, indicate your decision by
     deleting  the provisions above and replace  them with the notice and
     other provisions required by the MPL License.  If you do not delete
     the provisions above, a recipient may use your version of this file
     under either the MPL License.

     The contents of this file are subject to the Mozilla Public License
     Version 1.1 (the "License"); you may not use this file except in
     compliance with the License. You may obtain a copy of the License at
     http://www.mozilla.org/MPL/

    */

    /**
     * The date validator.
     *
     * @package    kassiop.system
     * @subpackage validators
     *
     * @author NairuS
     * @version 1.2 $Rev: 90 $ $Date: 2011-01-16 09:23:26 +0000 (Sun, 16 Jan 2011) $ $Author: nicolas.surian $
     */
    class DateValidator extends BaseValidator
    {
        /**
         * Defines the badFormat key.
         *
         * @var string
         */
        public static $badFormatKey      = 'badFormat';

        /**
         * Defines the dateFormatKey key.
         *
         * @var string
         */
        public static $dateFormatKey     = 'dateFormat';

        /**
         * Defines the dateFormatError key.
         *
         * @var string
         */
        public static $dateFormatErrorKey     = 'dateFormatError';

        /**
         * Defines the dateOutput key.
         *
         * @var string
         */
        public static $dateOutputKey     = 'dateOutput';

        /**
         * Defines the dateTimeOutput key.
         *
         * @var string
         */
        public static $dateTimeOutputKey = 'dateTimeOutput';

        /**
         * Defines the max key.
         *
         * @var string
         */
        public static $maxKey            = 'max';

        /**
         * Defines the max key.
         *
         * @var string
         */
        public static $minKey            = 'min';

        /**
         * Defines the max key.
         *
         * @var string
         */
        public static $withTimeKey       = 'withTime';

        /**
         * Defines all the keys of the date array. read-only.
         *
         * @var array
         */
        private static $dateKeys         = array( 'year', 'month', 'day', 'hour', 'minute', 'second' );

        /**
         * Returns the array of date keys excepted.
         *
         * @return array
         */
        public static function getDateKeys()
        {
            return self::$dateKeys ;
        }

        /**
         * Configures the current validator.
         *
         * If some options and messages are given in the ValidatorBase constructor
         * they will take precedence over the options and messages you configure
         * in this method.
         *
         * @return void
         *
         * @see ValidatorBase::configure()
         */
        protected function configure()
        {
            // configure messages
            $this->addMessage( self::$badFormatKey, '"%value%" does not match the date format (%' . self::$dateFormatKey . '%)' );
            $this->addMessage( self::$maxKey      , 'The date must be before %' . self::$maxKey . '%.' );
            $this->addMessage( self::$minKey      , 'The date must be after %' . self::$minKey . '%.' );
            $this->setMessage( self::$invalidKey  , '"%value%" is invalid.' );

            // configure options
            $this->addOption( self::$dateFormatKey      , 'd-m-Y'       ); // date input format.
            $this->addOption( self::$dateFormatErrorKey , 'jj/mm/aaaa'  ); // date input helper format.
            $this->addOption( self::$dateOutputKey      , 'Y-m-d'       ); // mysql date format output.
            $this->addOption( self::$dateTimeOutputKey  , 'Y-m-d H:i:s' ); // mysql datetime format output.
            $this->addOption( self::$maxKey             , null          ); // EXAMPLE: 2010-01-31
            $this->addOption( self::$minKey             , null          ); // EXAMPLE: 2010-01-31
            $this->addOption( self::$withTimeKey        , false         );
        }

        /**
         * Cleans the input value.
         *
         * @param  mixed $value  The input value
         * @return mixed The cleaned value
         * @throws ErrorBaseValidator
         */
        protected function doClean( $value )
        {
            $clean = $this->getEmptyValue() ;
            if( is_string( $value ) )
            {
                $clean = strtotime( str_replace( '/', '-', $value ) );
                if( $clean === false )
                {
                    throw new ErrorBaseValidator( $this, self::$badFormatKey , array( 'value' => $value, self::$dateFormatKey => $this->getOption( self::$dateFormatErrorKey ) ) ) ;
                }
                else
                {
                    $clean = $this->validDate( $clean ) ;
                }
            }
            elseif( is_array( $value ) )
            {
                $clean = $this->validDateArray( $value );
            }
            else
            {
                throw new ErrorBaseValidator( $this, self::$invalidKey , array( 'value' => $value ) ) ;
            }
            return $clean ;
        }

        /**
         * Returns true if the values is setted.
         *
         * @param  array   $values
         * @param  string  $key
         * @return boolean
         */
        private function isValueSet( $values, $key )
        {
            return isset( $values[$key] ) && !in_array( $values[$key], array( null, '' ), true );
        }

        /**
         * Valid an date or a datetime if the value is an array.
         *
         * @param array $values
         * @return string
         * @throws ErrorBaseValidator
         */
        private function validDateArray( array $values )
        {
            // All elements must be empty or a number.
            foreach( self::getDateKeys() as $key )
            {
                if( isset( $values[$key] ) && !preg_match( '#^\d+$#', $values[$key] ) && !empty( $values[$key] ) )
                {
                    throw new ErrorBaseValidator( $this, self::$invalidKey , array( 'value' => StringsHelper::arrayToString( $values ) ) ) ;
                }
            }

            // count the number of element empty.
            $empties =
                ( !isset( $values['year'] )  || !$values['year']  ? 1 : 0 ) +
                ( !isset( $values['month'] ) || !$values['month'] ? 1 : 0 ) +
                ( !isset( $values['day'] )   || !$values['day']   ? 1 : 0 ) ;

            if( $empties > 0 && $empties < 3 )
            {
                throw new ErrorBaseValidator( $this, self::$invalidKey , array( 'value' => StringsHelper::arrayToString( $values ) ) ) ;
            }
            elseif( $empties == 3 )
            {
                return $this->getEmptyValue() ;
            }

            $dateToValid = $values['year'] . '-' . $values['month'] . '-' . $values['day'];
            if( $this->getOption( self::$withTimeKey ) )
            {
                // If no element or only second or only minute are setted
                if(
                    !$this->isValueSet( $values, 'second' ) && !$this->isValueSet( $values, 'minute' ) && !$this->isValueSet( $values, 'hour' )
                    ||
                    $this->isValueSet( $values, 'second' ) && ( !$this->isValueSet( $values, 'minute' ) || !$this->isValueSet( $values, 'hour' ) )
                    ||
                    $this->isValueSet( $values, 'minute' ) && !$this->isValueSet( $values, 'hour' )
                  )
                {
                    throw new ErrorBaseValidator( $this, self::$invalidKey , array( 'value' => StringsHelper::arrayToString( $values ) ) ) ;
                }

                // test if the seconds are setted because it is not required.
                $second       = isset( $values['second'] ) ? intval( $values['second'] ) : 0 ;
                $dateToValid .= ' ' . intval( $values['hour'] ) . ':' . intval( $values['minute'] ) . ':' . $second ;
            }
            return $this->validDate( strtotime( $dateToValid ) );
        }

        /**
         *  Returns the date formatted if validated.
         *
         * @param  integer $timestamp
         * @return string
         * @throws ErrorBaseValidator
         */
        private function validDate( $timestamp )
        {
            $tempDate = getdate( $timestamp );
            if( checkdate( $tempDate['mon'], $tempDate['mday'], $tempDate['year'] ) )
            {
                if( $this->getOption( self::$maxKey ) != null )
                {
                    $maxDate = strtotime( $this->getOption( self::$maxKey ) ) ;
                    if( $timestamp > $maxDate )
                    {
                        throw new ErrorBaseValidator
                        (
                            $this,
                            self::$maxKey,
                            array( self::$maxKey => date( $this->getOption( self::$dateFormatKey ) , $maxDate ) )
                        ) ;
                    }
                }
                if( $this->getOption( self::$minKey ) != null )
                {
                    $minDate = strtotime( $this->getOption( self::$minKey ) ) ;
                    if( $timestamp < $minDate )
                    {
                        throw new ErrorBaseValidator
                        (
                            $this,
                            self::$minKey ,
                            array( self::$minKey => date( $this->getOption( self::$dateFormatKey ) , $minDate ) )
                        ) ;
                    }
                }
                if( $this->getOption( self::$withTimeKey ) === true )
                {
                    return date( $this->getOption( self::$dateTimeOutputKey ) , $timestamp );
                }
                return date( $this->getOption( self::$dateOutputKey ), $timestamp );
            }
            else
            {
                throw new ErrorBaseValidator( $this, self::$invalidKey , array( 'value' => $value ) ) ;
            }
        }
    }
?>